//
// SoapTrace.Net - A soap message monitoring utility. 
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Text;

namespace SoapTrace.Core.Formatters
{
	/// <summary>
	/// Base class for RichTextBox formatters.
	/// </summary>
	public abstract class RichTextBoxFormatter
	{
		/// <summary>
		/// The EM_LINEINDEX message retrieves the character index 
		/// of the first character of a specified line in a multiline edit control.
		/// A character index is the zero-based index of the character from 
		/// the beginning of the edit control. 
		/// </summary>
		private const int EM_LINEINDEX = 0x00BB;
		
		/// <summary>
		/// An application sends the WM_SETREDRAW message to a window to 
		/// allow changes in that window to be redrawn or to prevent changes 
		/// in that window from being redrawn. 
		/// </summary>
		private const int WM_SETREDRAW = 0xB;
		
		/// <summary>
		/// Private windows message constant.
		/// </summary>
		private const int WM_USER = 0x0400;
		
		/// <summary>
		/// Constant used to change the rich text box text colour.
		/// </summary>
		private const int CFM_COLOR	= 0x40000000;
		
		/// <summary>
		/// Constant indicating that the rich text box selected text
		/// should be affected.
		/// </summary>
		private const int SCF_SELECTION	= 0x0001;

		/// <summary>
		/// Windows message to sets the rich text box selection.
		/// </summary>
		private const int EM_EXSETSEL = WM_USER + 55;
		
		/// <summary>
		/// Windows message to change the rich text box character format.
		/// </summary>
		private const int EM_SETCHARFORMAT = WM_USER + 68;
		
		[ StructLayout( LayoutKind.Sequential ) ]
		struct CHARRANGE 
		{
  			public int cpMin;
  			public int cpMax;
		}
		
		[ StructLayout( LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto )]
		struct CHARFORMAT
		{
		    public int cbSize;
		    public int dwMask;
		    public int dwEffects;
		    public int yHeight;
		    public int yOffset;
		    public int crTextColor;
		    public byte bCharSet;
		    public byte bPitchAndFamily;
		    [ MarshalAs( UnmanagedType.ByValTStr, SizeConst = 32 ) ]
		    public string szFaceName;
		}
		
		/// <summary>
		/// Enables screen updates for the text box.
		/// </summary>
		/// <param name="textBox">A rich text box.</param>
		public static void EnableScreenPainting( RichTextBox textBox )
		{
			HandleRef handleRef = new HandleRef( textBox, textBox.Handle );
			SendMessage( handleRef, WM_SETREDRAW, 1, 0 );
			textBox.Invalidate( true );
		}
		
		/// <summary>
		/// Disables screen updates for the text box.
		/// </summary>
		/// <param name="textBox">A rich text box.</param>
		public static void DisableScreenPainting( RichTextBox textBox )
		{
            try
            {
                HandleRef handleRef = new HandleRef(textBox, textBox.Handle);
                SendMessage(handleRef, WM_SETREDRAW, 0, 0);
            }
            catch (InvalidOperationException)
            {
            }
		}
		
		/// <summary>
		/// Formats the text in the <paramref name="textBox"/> based on 
		/// the <paramref name="formattedText"/>.
		/// </summary>
		/// <param name="textBox">A rich text box.</param>
		/// <param name="formattedText">A formatted piece of text.</param>
		public static void Format( RichTextBox textBox, FormattedText formattedText )
		{
			try
			{
				DisableScreenPainting( textBox );
				
				textBox.Clear( );
				textBox.Text = formattedText.Text;

				int startIndex = 0;
				
				foreach( FormattedTextItem Item in formattedText.Items )
				{
				  RichTextBoxFormatter.SetTextColour( Item.Colour, textBox, startIndex, Item.Text.Length );
					startIndex += Item.Text.Replace( "\r\n", "\n" ).Length;
				}
			}
			finally
			{
				SelectText( textBox, 0, 0 );
				EnableScreenPainting( textBox );
			}
		}
		
		/// <summary>
		/// Sets the text colour.
		/// </summary>
		/// <param name="textColour">The text colour.</param>
		/// <param name="textBox">The rich text box containing the text.</param>
		/// <param name="lineNumber">The line number (starts from 1).</param>
		/// <param name="linePosition">The line position (starts from 1).</param>
		/// <param name="length">The length of text to colour.</param>
		protected void SetTextColour( Color textColour, RichTextBox textBox, int lineNumber, int linePosition, int length )
		{
			int index = GetCharIndex( textBox, lineNumber, linePosition );
			textBox.Select( index, length );
			textBox.SelectionColor = textColour;
		}		
		
		/// <summary>
		/// Gets the character index from the line and the line position.
		/// </summary>
		/// <param name="textBox">A rich text box.</param>
		/// <param name="lineNumber">The line number (starts from 1).</param>
		/// <param name="linePosition">The line position (starts from 1).</param>
		/// <returns>The character index (zero based).</returns>
		protected int GetCharIndex( RichTextBox textBox, int lineNumber, int linePosition )
		{
			// Get the character position at the start of the specified line.
			
  			int LineStartCharacterIndex = SendMessage( new HandleRef( textBox, textBox.Handle ), EM_LINEINDEX, lineNumber - 1, 0 );
  			
			int CharacterIndex = LineStartCharacterIndex + ( linePosition - 1 );
			
			return CharacterIndex;
		}
		
		/// <summary>
		/// Win32 SendMessage API call.
		/// </summary>
		[ DllImport( "user32.dll", CharSet=CharSet.Auto ) ]
		static extern int SendMessage( HandleRef hWnd, int msg, int wParam, int lParam );

		/// <summary>
		/// Win32 SendMessage API call.
		/// </summary>
		[ DllImport( "user32.dll", CharSet=CharSet.Auto ) ]
		static extern int SendMessage( HandleRef hWnd, int msg, int wParam, ref CHARRANGE charRange );

		/// <summary>
		/// Win32 SendMessage API call.
		/// </summary>
		[ DllImport( "user32.dll", CharSet=CharSet.Auto ) ]
		static extern int SendMessage( HandleRef hWnd, int msg, int wParam, ref CHARFORMAT charFormat );

		/// <summary>
		/// Sets the text colour.
		/// </summary>
		/// <param name="colour">The text colour.</param>
		/// <param name="textBox">The rich text box containing the text.</param>
		/// <param name="index">The start index of the text to colour.</param>
		/// <param name="length">The length of text to colour.</param>
		static void SetTextColour( Color colour, RichTextBox textBox, int index, int length )
		{
			SelectText( textBox, index, length );
			
			CHARFORMAT charFormat = new CHARFORMAT( );
			charFormat.cbSize = Marshal.SizeOf( typeof( CHARFORMAT ) );
			charFormat.dwMask = CFM_COLOR;
			charFormat.crTextColor = ColorTranslator.ToWin32( colour );

			SendMessage( new HandleRef( textBox, textBox.Handle ), EM_SETCHARFORMAT, SCF_SELECTION, ref charFormat );
		}
		
		/// <summary>
		/// Selects the rich text box text.
		/// </summary>
		/// <param name="textBox">The rich text box containing the text.</param>
		/// <param name="index">The start index of the text to colour.</param>
		/// <param name="length">The length of text to colour.</param>
		static void SelectText( RichTextBox textBox, int index, int length )
		{
			CHARRANGE charRange = new CHARRANGE( );
			charRange.cpMin = index;
			charRange.cpMax = index + length;
			SendMessage( new HandleRef( textBox, textBox.Handle ), EM_EXSETSEL, 0, ref charRange );
		}
	}
}
